#include <BaseMap.h>

int** lineGrid(int **basemap,int x0,int x1,int y0,int y1,int value)
{//bresenham's line algo
	bool steep = ( (abs(y1-y0) > abs(x1-x0))? true: false);
	if (steep)
	{//swap xo,y0 and x1,y1
		int temp;
		temp = x0;
		x0 = y0;
		y0 = temp;

		temp = x1;
		x1 = y1;
		y1 = temp;

	}
	if (x0 > x1)
	{//swap x0,x1 and swap y0,y1
		int temp;
		temp = x0;
		x0 = x1;
		x1 = temp;

		temp = y0;
		y0 = y1;
		y1 = temp;
	}
	int deltax = x1 - x0;
	int deltay = abs(y1 - y0);
	float error = 0.0;
	int ystep;
	int y = y0;
	ystep = ( (y0 < y1)? 1: -1);
	if (deltax != 0)
	{
		float deltaerr = ((float)deltay / (float)deltax);
		for (int x = x0; x <= x1; ++x)
		{
			if (steep)
			{
				basemap[y][x] = value;
			}
			else
			{
				basemap[x][y] = value;
			}
			error = error + deltaerr;
			if (error >= .5)
			{
				y = y + ystep;
				error = error - 1.0;
			}
		}
	}
	else
	{
		while ( y < y1)
		{
			basemap[x0][y] = value;
			y = y + ystep;
		}
		basemap[x0][y] = value;

	}
	return basemap;
}


float meanfn(float *numbers, int size)
{
	float sum = 0.00;
	for(int i = 0; i<size;++i)
	{
		sum += *(numbers + i);
	}
	return (sum/size);
}

float varfn(float *numbers, int size)
{
	float mean = meanfn(numbers,size);
	float var = 0.00;
	for (int i = 0; i<size; ++i)
	{
		//TODO check if storing *(numbers+i) in a local float is faster
		var += (*(numbers + i ) - mean) * (*(numbers + i ) - mean);
	}
	return (var/size);
}

int maxfn(float *numbers, int size)
{
 int maxindex = 0;
 int i = 0;
 while (i <= size)
 {
	if (*(numbers + i) > *(numbers + maxindex))
		maxindex = i;
	++i;
 }
 return maxindex;
}

float edistfn(float **matrix1,float **matrix2,int sizeX,int sizeY)
{
	double sum = 0.0;
	for(int i = 0; i < sizeX; ++i)
	{
		for(int j = 0; j < sizeY; ++j)
		{
			sum += (double)(matrix1[i][j] - matrix2[i][j]) * (double)(matrix1[i][j] - matrix2[i][j]);
		}
	}
	return ((float)sqrt(sum));
}

BaseMap::BaseMap(EchoParser *aparsedSong)
{
	parsedSong = aparsedSong;
	segments = parsedSong->getSegments();
	numberOfSegments = segments->size();

	for (int j = 0; j<12; ++j)
	{
		pitch[j] = (float*)malloc(sizeof(float)*numberOfSegments);
		timbre[j] = (float*)malloc(sizeof(float)*numberOfSegments);
		for (int i = 0; i<= numberOfSegments-1; ++i)
		{
		*(pitch[j] + i) = (*segments)[i]->getPitch(j);
		*(timbre[j] + i) = (*segments)[i]->getTimbre(j);
		//cout<<(*segments)[i]->getNumTatums()<<'\n';
		}
		
	}

}

void BaseMap::findPoints()
{
	float pitchvar[12], timbrevar[12];
	for (int i = 0; i<12;++i)
	{
		pitchvar[i] = varfn(pitch[i], numberOfSegments);
		timbrevar[i] = varfn(timbre[i], numberOfSegments);
	//  cout<<"the variance "<<i<<" is pitch = "<<pitchvar[i]<<" and timbre = "<<timbrevar[i]<<"\n";
	}

	int max_p,max_t;
	max_p = maxfn(pitchvar, 12);
	max_t = maxfn(timbrevar, 12);
	//cout<<max_p<<" "<<max_t;

	float max_t_value = timbre[max_p][maxfn(timbre[max_p], numberOfSegments)];

	points = new vector<Point*>(); //regular points
	//points = (vector<Point*>*)malloc(sizeof(Point)*numberOfSegments);
	Point *apoint = new Point;
	apoint->time = 0.0;
	apoint->x = STARTING_X;
	apoint->y = STARTING_Y;
	apoint->points = NULL;
	points->push_back(apoint);
	float prevAngle = STARTING_ANGLE ;
	float oldX = STARTING_X_OFFSET;
	float oldY = STARTING_Y_OFFSET;

	maxX = oldX; //
	maxY = oldY; // these are used to define the size of the grid i.e. the basemap

	for (int i = 1; i<= numberOfSegments-1;i++)
	{
		Point *apoint = new Point;
		apoint->time = (*segments)[i]->getStart();
		apoint->x = (float)(( (double)(*segments)[i-1]->getDuration() * cos((double)prevAngle * (22/7) /180)) * DISTANCE_MULTIPLIER) + oldX;
		apoint->y = (float)(( (double)(*segments)[i-1]->getDuration() * sin((double)prevAngle * (22/7) /180)) * DISTANCE_MULTIPLIER) + oldY;
		apoint->points = NULL;
		

		float delta = 0;
		if(apoint->y > SCREEN_HEIGHT - MASK_SIZE || apoint->y < 0 + MASK_SIZE)
		{
			delta = apoint->y - oldY;
			apoint->y = apoint->y - delta;
		}
		if(apoint->x > SCREEN_WIDTH - MASK_SIZE || apoint->x < 0 + MASK_SIZE)
		{
			delta = apoint->x - oldX;
			apoint->x = apoint->x - delta;
		}
		points->push_back(apoint);

		double comp = (double)(*(pitch[max_p] + i)) * (double)(*(timbre[max_t] + i)) / max_t_value;
		float newAngle = (int)((asin(comp) * 180 * 7 /22) * 100 )%ANGLE_BOUND;
		prevAngle = prevAngle + abs(newAngle) - ANGLE_BOUND/2;
		oldX = apoint->x;
		oldY = apoint->y;
		
		//update the maxX and maxY for the grid
		if (oldX > maxX)
		{
			maxX = oldX;
		}
		if (oldY > maxY)
		{
			maxY = oldY;
		}
		
	}

}

void BaseMap::audioTexture()
{
	//create the audio texture path between the given points
	//1st sample pitch and timbre at timbre rate
	vector<Tatum*>* tatums = new vector<Tatum*>();
	int numberOfTatums = parsedSong->getNumTatums();
	int currentTatum = 0;
	for (int i = 0; i <= numberOfSegments - 1;++i)
	{
		if(currentTatum > numberOfTatums)
			{break;}
		int numTatumInSegment = (*segments)[i]->getNumTatums();
		for (int j = 0; j < numTatumInSegment; ++j)
		{
			Tatum* atatum = new Tatum;
			atatum->segNumber = i;
			for (int k =0; k <12; ++k)
			{
				atatum->pitch[k] = (*segments)[i]->getPitch(k);
				atatum->timbre[k] = (*segments)[i]->getTimbre(k);
			}
			atatum->time = parsedSong->getTatum(currentTatum);
			atatum->audioTextureTatumNumber = -1;
			++currentTatum;
			tatums->push_back(atatum);
		}

	}
	//2nd the tatum number to which a tatum can jump to if the euclidean didtance is less than the threshold -> AUDIO_TEXTURE_THRESHOLD
	int numTatumPerSecond = (int)(parsedSong->getNumTatums() / parsedSong->getScalarAttr("usableDuration"));
	currentTatum = 0;
	int matrixSize = numTatumPerSecond * MATRIX_SIZE;
	for (int i = 0 + matrixSize/2; i < numberOfTatums - (numTatumPerSecond * (TIME_OFFSET + TIME_RANGE)) - matrixSize/2; ++i)
	{
		//compute the min euclidean distance of current tatum with all tatums between TIME_OFFSET seconds from it and (TIME_OFFSET + TIME_RANGE) from it.
		float euclideanDist = AUDIO_TEXTURE_THRESHOLD;
		float *TatumiMatrix[12];// = new float*[12]; 
		for (int l = 0; l <12; ++l)
		{
			TatumiMatrix[l] = new float[matrixSize];
			for (int k = i - matrixSize/2; k < i+ matrixSize/2; ++k) //to keep the tatumiMatrix centered at i matrixSize/2 is added and subtracted
				{
					*(TatumiMatrix[l] + k - (i - matrixSize/2)) = (*tatums)[k]->pitch[l];
					//*(TatumiMatrix[l] + k - (i - matrixSize/2)) = (*tatums)[k]->timbre[l];
				}
		}

		for(int j = i + (numTatumPerSecond * TIME_OFFSET); j < i + (numTatumPerSecond * (TIME_OFFSET + TIME_RANGE));++j)
		{
			float newEuclideanDist;
			float *TatumjMatrix[12];// = new float*[12] 
			for (int l = 0; l <12; ++l)
			{
				TatumjMatrix[l] = new float[matrixSize];
				for (int k = j - matrixSize/2; k < j+ matrixSize/2; ++k)
				{
					*(TatumjMatrix[l] + k - (j - matrixSize/2)) = (*tatums)[k]->pitch[l];
					//*(TatumjMatrix[l] + k - (j - matrixSize/2)) = (*tatums)[k]->timbre[l];
				}
			}
			newEuclideanDist = edistfn(TatumiMatrix,TatumjMatrix,12,matrixSize);
			if(newEuclideanDist < euclideanDist)
			{
				euclideanDist = newEuclideanDist;
				(*tatums)[i]->audioTextureTatumNumber = j;
			}
		}
		if ((*tatums)[i]->audioTextureTatumNumber > -1) {i += (*tatums)[i]->audioTextureTatumNumber;} //to prevent getting more atpoints between the current one and its ATtexture point
	}


	//3rd insert the audio textured tatum and the tatum to which it is audio textured after the 
	//    point correspoind to their respective segment in the points vector
	ATpoints = new vector<Point*>();
	for (int i = 0; i <= numberOfTatums - 1; ++i)
	{
		if( (*tatums)[i]->audioTextureTatumNumber > -1) //check if this 
		{
			Point *apoint = new Point;
			apoint->time = (*tatums)[i]->time;
			float x1 = (*points)[(*tatums)[i]->segNumber]->x; //x value of the point corresponding to the segment tatum i is in
			float y1 = (*points)[(*tatums)[i]->segNumber]->y; //y value of the point corresponding to the segment tatum i is in
			float x2 = (*points)[(*tatums)[i]->segNumber+1]->x; //x value of the next point 
			float y2 = (*points)[(*tatums)[i]->segNumber+1]->y; //y value of the next point 
			double m;
			if (x2 != x1)
			{//TODO handle m = infinity. right now it is just ignored
				m = ((double)y2 - (double)y1)/((double)x2 - (double)x1);
				double theta = atan(m); //theta is in radians
				float length = (apoint->time) - (*points)[(*tatums)[i]->segNumber]->time;
				apoint->x = length * (float)cos(theta) + x1;
				apoint->y = length * (float)sin(theta) + y1;
				apoint->points = new Point;
				int j = (*tatums)[i]->audioTextureTatumNumber;
				apoint->points->time = (*tatums)[j]->time;
				x1 = (*points)[(*tatums)[j]->segNumber]->x; //x value of the point corresponding to the segment tatum i is in
				y1 = (*points)[(*tatums)[j]->segNumber]->y; //y value of the point corresponding to the segment tatum i is in
				x2 = (*points)[(*tatums)[j]->segNumber+1]->x; //x value of the next point 
				y2 = (*points)[(*tatums)[j]->segNumber+1]->y; //y value of the next point 
				if (x2 != x1)
				{//TODO handle m = infinity. right now it is just ignored
					m = (y2 - y1)/(x2 - x1);
					theta = atan(m); //theta is in radians
					length = (apoint->points->time) - (*points)[(*tatums)[j]->segNumber]->time;
					apoint->points->x = length * (float)cos(theta) + x1;
					apoint->points->y = length * (float)sin(theta) + y1;
					apoint->points->points = NULL;
					ATpoints->push_back(apoint);
				}
			}

			//apoint->points = (*tatums)[(*tatums)[i]->audioTextureTatumNumber]
		}
	}

}




void BaseMap::createBaseMap(int mode)
{
	gridsize = max(maxX,maxY) * RESOLUTION + MASK_SIZE*2 + 1;
	abasemap = new int*[gridsize];
	for (int i = 0; i<gridsize;++i)
	{
		abasemap[i] = new int[gridsize];
		memset(abasemap[i], 0, gridsize*sizeof(int));
	}											

	if(mode == 2 || mode == 3) //only for normal points
	{
		for (int i = 0; i< numberOfSegments - 1; ++i) //note only going to the last but one point as there is no line after the last point
			{
				int xi = (int)((*points)[i]->x * RESOLUTION);
				if (xi >= gridsize) --xi;
				int yi = (int)((*points)[i]->y * RESOLUTION);
				if (yi >= gridsize) --yi;
				//slope of the line xi,yi -> xi+1,yi+1
				int xiplus1 = (int)((*points)[i+1]->x * RESOLUTION);
				if (xiplus1 >= gridsize) --xiplus1;
				int yiplus1 = (int)((*points)[i+1]->y * RESOLUTION);
				if (yiplus1 >= gridsize) --yiplus1;
				abasemap = lineGrid(abasemap,xi,xiplus1,yi,yiplus1,1);
			}	
	}
	if (mode == 1 || mode == 3)
	{//creating for atpoints
		for (int i = 0; i < ATpoints->size(); ++i)
		{
			int xi = (int)((*ATpoints)[i]->x * RESOLUTION);
			if (xi >= gridsize) --xi;
			int yi = (int)((*ATpoints)[i]->y * RESOLUTION);
			if (yi >= gridsize) --yi;
			int xiplus1 = (int)((*ATpoints)[i]->points->x * RESOLUTION);
			if (xiplus1 >= gridsize) --xiplus1;
			int yiplus1 = (int)((*ATpoints)[i]->points->y * RESOLUTION);
			if (yiplus1 >= gridsize) --yiplus1;
			abasemap = lineGrid(abasemap,xi,xiplus1,yi,yiplus1,1);
		}
	}
}

void BaseMap::smoothBaseMap()
{
	int **smoothBasemap = new int*[gridsize];
	for (int i = 0; i<gridsize;++i)
	{
		smoothBasemap[i] = new int[gridsize];
		memset(smoothBasemap[i], 0, gridsize*sizeof(int));
	}

	for (int i = MASK_SIZE; i < gridsize - MASK_SIZE; ++i)
	 {
		 for (int j = MASK_SIZE; j < gridsize - MASK_SIZE; ++j)
		 {
			 for (int m = i - (MASK_SIZE -1)/2; m <= i + (MASK_SIZE -1)/2; ++m)
				for (int n = j - (MASK_SIZE -1)/2; n <= j + (MASK_SIZE -1)/2; ++n)
					if(abasemap[m][n] != 0)
						smoothBasemap[i][j] = abasemap[m][n];
		 }
	}
	abasemap = smoothBasemap;
}

void BaseMap::printMap()
{
	for (int i = 0; i < gridsize; ++i)
	 {
		 for (int j = 0; j < gridsize; ++j)
		 {
			if (*( *(abasemap + j) + i) == 0)
				cout<<" ";
			else
			cout<<*( *(abasemap + j) + i);
		 }
		 //getch();
		 cout<<"\n";
	 }
	//getch();
}

int** BaseMap::getBaseMap()
{
	return abasemap;
}

vector<Point*>* BaseMap::getPoints()
{
	return points;
}

vector<Point*>* BaseMap::getATPoints()
{
	return ATpoints;
}

void BaseMap::write(int** map, int h, int w)
{
	//
	cout << "Call write()" << endl;
	int x,y, i, cell;
	//pngwriter png(h * BOX_TO_PIXEL, w * BOX_TO_PIXEL ,1,"cargameheightmap.png");
	png::image< png::gray_pixel > png(h * BOX_TO_PIXEL, w * BOX_TO_PIXEL);
	double r, g, b;
	//for(y = 0; y < h; y++)
	for(y = 0; y < png.get_height(); y++)
	{
		//for(x = 0; x < w; x++)
		for(x = 0; x < png.get_width(); x++)
		{	
			//printf("Pixel R: %f, G: %f, B: %f\n @ (%d, %d)", r, g, b, x, y);
			
			//cell = map[(y * w) + x];
			cell = *(*(map + x ) + y);
			if(cell == 1)//gray
			{
				//png.plot(x + 1, y + 1, 0.0, 0.0, 0.0);
				
				/*
				NOTE:
				We may have to take another look at this call
				to make sure it's rendering our heightmaps correctly,
				but you should be able to compile this
				*/
				
				png[y][x] = png::gray_pixel(1);
			}
			else if(cell == 2)
			{
				//png.plot(x + 1, y + 1, 0.50, 0.50, 0.50);
			}
			else if (cell == 0)//black
			{
				//png.plot(x + 1, y + 1, 1.0, 1.0, 1.0);
				png[y][x] = png::gray_pixel(0);
			}
		}
	}
	png.write("carheightmap.png");
}
void BaseMap::writeheightmap()
{
	write(abasemap,gridsize,gridsize);
}


